#include "userprog/syscall.h"
#include "user/syscall.h"
#include <stdio.h>
#include <syscall-nr.h>
#include "threads/interrupt.h"
#include "threads/thread.h"
#include "userprog/process.h"
#include <threads/vaddr.h>
#include "pagedir.h"
#include <filesys/filesys.h>
#include <filesys/file.h>
#include <stdlib.h>

static void syscall_handler(struct intr_frame *);

/* system call function */
void sys_halt(void);
void sys_exit(int status);
pid_t sys_exec(const char *cmd_line);
int sys_wait(pid_t pid);
bool sys_create(const char *file, unsigned initial_size);
bool sys_remove(const char *file);
int sys_open(const char *file);
int sys_filesize(int fd);
int sys_read(int fd, void *buffer, unsigned size);
int sys_write(int fd, const void *buffer, unsigned size);
void sys_seek(int fd, unsigned position);
unsigned sys_tell(int fd);
void sys_close(int fd);

void syscall_init(void)
{
  intr_register_int(0x30, 3, INTR_ON, syscall_handler, "syscall");
}

/* from doc */
static int
get_user(const uint8_t *uaddr)
{
  int result;
  asm("movl $1f, %0; movzbl %1, %0; 1:"
      : "=&a"(result)
      : "m"(*uaddr));
  return result;
}
/* exception */
void exit_special(void)
{
  thread_current()->exit_code = -1;
  thread_exit();
}
/* 检查页面和地址合法性 */
void *
is_addr_valid(const void *vaddr)
{
  /* 判断地址合法性 */
  if (!is_user_vaddr(vaddr))
  {
    exit_special();
  }
  /*判断页面合法性*/
  void *ptr = pagedir_get_page(thread_current()->pagedir, vaddr);
  if (!ptr)
  {
    exit_special();
  }
  /* 判断页面内容合法性*/
  uint8_t *check_byteptr = (uint8_t *)vaddr;
  for (uint8_t i = 0; i < 4; i++)
  {
    if (get_user(check_byteptr + i) == -1)
    {
      exit_special();
    }
  }

  return ptr;
}

/* 根据fd查询文件 */
struct thread_file *
find_file_id(int file_id)
{
  struct list_elem *e;
  struct thread_file *thread_file_temp = NULL;
  struct list *files = &thread_current()->files;
  for (e = list_begin(files); e != list_end(files); e = list_next(e))
  {
    thread_file_temp = list_entry(e, struct thread_file, file_elem);
    if (file_id == thread_file_temp->fd)
      return thread_file_temp;
  }
  return false;
}

static void
syscall_handler(struct intr_frame *f)
{
  int *p = f->esp;
  is_addr_valid(p + 1);

  /* get system call type*/
  int system_call_type = *(int *)f->esp;
  if (system_call_type <= 0 || system_call_type >= 20)
  {
    exit_special();
  }

  /* navigate to function handler */
  uint32_t *esp = f->esp;

  switch (system_call_type)
  {
  case SYS_HALT:
    sys_halt();
    break;

  case SYS_EXIT:
    is_addr_valid(esp + 1);
    sys_exit(*(esp + 1));
    break;

  case SYS_EXEC:
    is_addr_valid(esp + 1);
    is_addr_valid(*(esp + 1));
    f->eax = sys_exec(*(esp + 1));
    break;

  case SYS_WAIT:
    is_addr_valid(esp + 1);
    f->eax = sys_wait(*(esp + 1));
    break;

  case SYS_CREATE:
    is_addr_valid(esp + 2);
    is_addr_valid(esp + 1);
    is_addr_valid(*(esp + 1));
    acquire_lock_f();
    f->eax = sys_create(*(esp + 1), *(esp + 2));
    release_lock_f();
    break;

  case SYS_REMOVE:
    is_addr_valid(esp + 1);
    is_addr_valid(*(esp + 1));
    acquire_lock_f();
    f->eax = sys_remove(*(esp + 1));
    release_lock_f();
    break;

  case SYS_OPEN:
    is_addr_valid(esp + 1);
    is_addr_valid(*(esp + 1));
    f->eax = sys_open(*(esp + 1));
    break;

  case SYS_FILESIZE:
    is_addr_valid(esp + 1);
    acquire_lock_f();
    f->eax = sys_filesize(*(esp + 1));
    release_lock_f();
    break;

  case SYS_READ:

    is_addr_valid(esp + 1);
    is_addr_valid(esp + 2);
    is_addr_valid(*(esp + 2));
    is_addr_valid(esp + 3);
    acquire_lock_f();
    f->eax = sys_read(*(esp + 1), *(esp + 2), *(esp + 3));
    release_lock_f();
    break;

  case SYS_WRITE:
    is_addr_valid(esp + 1);
    is_addr_valid(esp + 2);
    is_addr_valid(*(esp + 2));
    is_addr_valid(esp + 3);
    acquire_lock_f();
    f->eax = sys_write(*(esp + 1), *(esp + 2), *(esp + 3));
    release_lock_f();
    break;

  case SYS_SEEK:
    is_addr_valid(esp + 1);
    is_addr_valid(esp + 2);
    sys_seek(*(esp + 1), *(esp + 2));
    break;

  case SYS_TELL:
    is_addr_valid(esp + 1);
    acquire_lock_f();
    f->eax = sys_tell(*(esp + 1));
    release_lock_f();
    break;

  case SYS_CLOSE:
    is_addr_valid(esp + 1);
    sys_close(*(esp + 1));
    break;

  default:
    printf("Unknown system call type! ===== %d =====\n", system_call_type);
  }
}

void sys_halt(void)
{
  shutdown_power_off();
}

void sys_exit(int status)
{
  struct thread *t = thread_current();
  t->exit_code = status;
  thread_exit();
}

pid_t sys_exec(const char *cmd_line)
{
  return process_execute(cmd_line);
}

int sys_wait(pid_t pid)
{
  return process_wait(pid);
}

bool sys_create(const char *file, unsigned initial_size)
{
  return filesys_create(file, initial_size);
}

bool sys_remove(const char *file)
{
  return filesys_remove(file);
}

int sys_open(const char *file)
{
  acquire_lock_f();
  struct file *file_opened = filesys_open(file);
  release_lock_f();
  struct thread *t = thread_current();
  if (file_opened)
  {
    struct thread_file *thread_file_temp = malloc(sizeof(struct thread_file));
    thread_file_temp->fd = t->file_fd++;
    thread_file_temp->file = file_opened;
    list_push_back(&t->files, &thread_file_temp->file_elem);
    return thread_file_temp->fd;
  }
  else
  {
    return -1;
  }
}

int sys_filesize(int fd)
{
  struct thread_file *thread_file_temp = find_file_id(fd);
  if (thread_file_temp)
  {
    return file_length(thread_file_temp->file);
  }
  else
  {
    return -1;
  }
}

int sys_read(int fd, void *buffer, unsigned size)
{
  if (fd == 0)
  {
    int i;
    for (i = 0; i < size; i++)
    {
      (*((char **)buffer))[i] = input_getc();
    }
    return size;
  }
  struct thread_file *opened_file = find_file_id(fd);
  if (opened_file == NULL)
  {
    return -1;
  }
  return file_read(opened_file->file, buffer, size);
}

int sys_write(int fd, const void *buffer, unsigned size)
{
  if (fd == 1)
  {
    int i;
    putbuf(buffer, size);
    return size;
  }
  struct thread_file *opened_file = find_file_id(fd);
  if (opened_file == NULL)
  {
    return 0;
  }
  return file_write(opened_file->file, buffer, size);
}

void sys_seek(int fd, unsigned position)
{
  struct thread_file *opened_file = find_file_id(fd);
  if (opened_file)
  {
    acquire_lock_f();
    file_seek(opened_file->file, position);
    release_lock_f();
  }
}

unsigned sys_tell(int fd)
{
  struct thread_file *opened_file = find_file_id(fd);
  if (fd == NULL)
  {
    return -1;
  }
  return file_tell(opened_file->file);
}

void sys_close(int fd)
{
  struct thread_file *opened_file = find_file_id(fd);
  if (opened_file)
  {
    acquire_lock_f();
    file_close(opened_file->file);
    release_lock_f();
    /* Remove the opened file from the list */
    list_remove(&opened_file->file_elem);
    /* Free opened files */
    free(opened_file);
  }
}